﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Faml.Frontend;
using Faml.LinqCompiler;

namespace Faml.Optimization
{
    public class ExpressionOptimizer
    {
        private FamlExpression expression;
        private List<IVisitor> visitors;

        private bool removeCommonExpressions = true;

        public ExpressionOptimizer(FamlExpression expression)
        {
            this.expression = expression;
            this.visitors = new List<IVisitor>();
        }

        public void AddVisitor(IVisitor visitor)
        {
            visitors.Add(visitor);
        }

        public void AddVisitors(IEnumerable<IVisitor> visitors)
        {
            this.visitors.AddRange(visitors);
        }

        public LinkedList<FamlExpressionContainer> Optimize()
        {
            LinkedList<FamlExpressionContainer> result;

            // Visitors
            FamlExpression afterVisit = ApplyVisitors(expression, visitors);

            // Common Expressions Elimination            

            if (removeCommonExpressions)
            {
                CommonExpressionEliminator eliminator = new CommonExpressionEliminator(afterVisit);
                eliminator.Optimize();

                LinkedList<FamlExpressionContainer> sequence = eliminator.OrderedExpressions;
                if (sequence.Count > 0)
                {
                    LinkedListNode<FamlExpressionContainer> node = sequence.First;

                    do
                    {
                        LambdaOptimizer lo = new LambdaOptimizer(node.Value);
                        if (lo.Optimize())
                        {
                            LinkedList<FamlExpressionContainer> list = lo.OrderedExpressions;
                            foreach (FamlExpressionContainer c in list)
                            {
                                sequence.AddBefore(node, c);
                            }

                            LinkedListNode<FamlExpressionContainer> removeNode = node;
                            node = node.Next;
                            sequence.Remove(removeNode);
                        }
                        else
                        {
                            node = node.Next;
                        }

                    } while (node != null);

                    result = sequence;
                }
                else
                {
                    result = new LinkedList<FamlExpressionContainer>();
                    result.AddFirst(new FamlExpressionContainer(afterVisit));
                }
            }
            else
            {
                result = new LinkedList<FamlExpressionContainer>();
                result.AddFirst(new FamlExpressionContainer(afterVisit));
            }

            return result;
        }

        private static FamlExpression ApplyVisitors(FamlExpression expression, IEnumerable<IVisitor> visitors)
        {
            if (visitors == null)
                return expression;

            foreach (IVisitor visitor in visitors)
            {
                try
                {
                    expression = VisitOptimizer.Optimize(expression, visitor);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("Visitor " + visitor.ToString() + " couldn't be applied. Skipping it.");
                    Debug.WriteLine("(Exception: " + ex.Message + ")");
                }
            }

            return expression;
        }
    }
}
